/*
 * INET		An implementation of the TCP/IP protocol suite for the LINUX
 *		operating system.  INET is implemented using the  BSD Socket
 *		interface as the means of communication with the user level.
 *
 *		ROUTE - implementation of the IP router.
 *
 * Version:	@(#)route.c	1.0.14	05/31/93
 *
 * Authors:	Ross Biro, <bir7@leland.Stanford.Edu>
 *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
 *		Alan Cox, <gw4pts@gw4pts.ampr.org>
 *		Linus Torvalds, <Linus.Torvalds@helsinki.fi>
 *
 * Fixes:
 *		Alan Cox	:	Verify area fixes.
 *		Alan Cox	:	cli() protects routing changes
 *		Rui Oliveira	:	ICMP routing table updates
 *		(rco@di.uminho.pt)	Routing table insertion and update
 *		Linus Torvalds	:	Rewrote bits to be sensible
 *		Alan Cox	:	Added BSD route gw semantics
 *		Alan Cox	:	Super /proc >4K 
 *		Alan Cox	:	MTU in route table
 *		Alan Cox	: 	MSS actually. Also added the window
 *					clamper.
 *		Sam Lantinga	:	Fixed route matching in rt_del()
 *		Alan Cox	:	Routing cache support.
 *		Alan Cox	:	Removed compatibility cruft.
 *		Alan Cox	:	RTF_REJECT support.
 *		Alan Cox	:	TCP irtt support.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 */

#include <asm/segment.h>
#include <asm/system.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/errno.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <net/route.h>
#include <net/tcp.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <net/icmp.h>

/*
 *	The routing table list
 */
//路由表
static struct rtable *rt_base = NULL;
unsigned long rt_stamp = 1;		/* 路由表有更新，此值就会加1，0值不会被使用Routing table version stamp for caches ( 0 is 'unset' ) */

/*
 *	Pointer to the loopback route
 */
//lo设备的路由表 
static struct rtable *rt_loopback = NULL;

/*
 *	Remove a routing table entry.
 */
//删除一个路由表项
//@dst:目的地址
//@devname:网卡名称
static void rt_del(unsigned long dst, char *devname)
{
	struct rtable *r, **rp;
	unsigned long flags;
	
	//rp二级指针，指向&rt_base，单向链表删除技巧
	rp = &rt_base;
	
	/*
	 *	This must be done with interrupts off because we could take
	 *	an ICMP_REDIRECT.
	 */
	 
	save_flags(flags);
	cli();
	while((r = *rp) != NULL) 
	{
		/* Make sure both the destination and the device match */
		if ( r->rt_dst != dst ||
		(devname != NULL && strcmp((r->rt_dev)->name,devname) != 0) )
		{
			rp = &r->rt_next;
			continue;
		}
		//目的地址和网卡名称都一样删除
		*rp = r->rt_next;
		
		/*
		 *	If we delete the loopback route update its pointer.
		 */
		 
		if (rt_loopback == r)
			rt_loopback = NULL;
		kfree_s(r, sizeof(struct rtable));
	} 
	rt_stamp++;		/* New table revision */
	
	restore_flags(flags);
}


/*
 *	Remove all routing table entries for a device. This is called when
 *	a device is downed.
 */
//此函数是个回调函数，当有网卡关闭时(ifconfig eth0 down)，会调用此函数。
//@dev:关闭的网卡
//此函数会把与此网卡相关的路由表全部删除掉
void ip_rt_flush(struct device *dev)
{
	struct rtable *r;
	struct rtable **rp;
	unsigned long flags;

	rp = &rt_base;
	save_flags(flags);
	cli();
	while ((r = *rp) != NULL) {
		if (r->rt_dev != dev) {
			rp = &r->rt_next;
			continue;
		}
		*rp = r->rt_next;
		if (rt_loopback == r)
			rt_loopback = NULL;
		kfree_s(r, sizeof(struct rtable));
	} 
	rt_stamp++;		/* New table revision */
	restore_flags(flags);
}

/*
 *	Used by 'rt_add()' when we can't get the netmask any other way..
 *
 *	If the lower byte or two are zero, we guess the mask based on the
 *	number of zero 8-bit net numbers, otherwise we use the "default"
 *	masks judging by the destination address and our device netmask.
 */
//根据dst获取其子网掩码
//
static inline unsigned long default_mask(unsigned long dst)
{
	dst = ntohl(dst);
	if (IN_CLASSA(dst)) //判断是否是A类网
		return htonl(IN_CLASSA_NET);  //是，返回A类网的子网掩码
	if (IN_CLASSB(dst)) //判断是否是B类网
		return htonl(IN_CLASSB_NET);  //是，返回B类网的子网掩码
	return htonl(IN_CLASSC_NET); //如果都不是，返回C类网的子网掩码
}


/*
 *	If no mask is specified then generate a default entry.
 */
//猜网络掩码
//
static unsigned long guess_mask(unsigned long dst, struct device * dev)
{
	unsigned long mask;

	if (!dst) //如果目的地址是0，那返回0，mask取0，默认网关就是这样
		return 0;
	mask = default_mask(dst);  //根据dst获取默认mask
	if ((dst ^ dev->pa_addr) & mask) //如果dst和dev不在同一网段，则返回mask
		return mask;
	return dev->pa_mask; //如果在同一网段，返回dev的mask
}


/*
 *	Find the route entry through which our gateway will be reached
 */
//当添加带有网关ip的路由表项时，网关ip一定要和本机的网卡ip在同一网段
//此函数是在路由表中查找从哪个网卡发送数据可以到达网关gw标示的主机
//
static inline struct device * get_gw_dev(unsigned long gw)
{
	struct rtable * rt;

	for (rt = rt_base ; ; rt = rt->rt_next) //遍历路由表链表
	{
		if (!rt)//到达链表尾，返回NULL
			return NULL;
		//网卡设备在启动后，先建立一个直接路由，然后添加默认路由，默认路由的网关必须和
		//直接路由的Destination在同一网段，也就是下面的判断
		//例子：执行ifconfig eth0 up
		//此命令完成以下几步：1.将网卡启动设置其ip，例如192.168.1.100
		//2.添加直接路由route add -net 192.168.1.0/24 eth0
		//此时路由表情况：
		//estination     Gateway         Genmask         Flags  Iface 
		//192.168.1.0     0.0.0.0         255.255.255.0   U      ens33
		//注意：这里面删减了一些不关心字段
		//3.添加默认路由
		//route add default gw 192,168.1.1
		//此是路由表情况：
		//estination     Gateway         Genmask         Flags Iface
		//0.0.0.0         192.168.1.1     0.0.0.0         UG    ens33
		//192.168.1.0     0.0.0.0         255.255.255.0   U     ens33
		if ((gw ^ rt->rt_dst) & rt->rt_mask)//如果gw和rt->dst不在同一网段，继续循环
			continue;
		/* 
		 *	Gateways behind gateways are a no-no 
		 */
		//下面的判断避免gw是个不可到达的网关，如下解释
		//如果此条路由表项设置了RTF_GATEWAY，说明是一条间接路由表项，例如，执行
		//route add -host 192.168.2.21 gw 192.168.1.1 ens33  又添加了一条路由表项
		//此时路由表情况：
		//Destination     Gateway         Genmask         Flags Iface
		//0.0.0.0         192.168.1.1     0.0.0.0         UG     ens33
		//192.168.1.0     0.0.0.0         255.255.255.0   U      ens33
		//192.168.2.21    192.168.1.1     255.255.255.255 UGH    ens33
		//添加此表项，是为了本机如果向将数据发送到2网段的21主机，就要将数据发送给网关192.168.1.1
		//由网关来转发，将数据发送给2网段的网关，由2网段的网关发送给21主机。
		//如果用户把默认路由删除，然后执行以下命令
		//route add default gw 192.168.2.1
		//添加一个路由表项，则如果没有以下判断，则这条路由就会添加成功，
		//错误的路由表情况:
		//Destination     Gateway         Genmask         Flags Iface
		//0.0.0.0         192.168.2.1     0.0.0.0         UG     ens33
		//192.168.1.0     0.0.0.0         255.255.255.0   U      ens33
		//192.168.2.21    192.168.1.1     255.255.255.255 UGH    ens33
		//但是其实此路由表项是不合法的，因为
		//本机是在1网段，路由到默认网关的数据包必须发送到1网段的网关即192.168.1.1，而网关被设置成了2网段，被
		//路由到默认路由的数据包根本发送不到此网关
		if (rt->rt_flags & RTF_GATEWAY)
			return NULL;
		return rt->rt_dev;//返回网卡设备
	}
}

/*
 *	Rewrote rt_add(), as the old one was weird - Linus
 *
 *	This routine is used to update the IP routing table, either
 *	from the kernel (ICMP_REDIRECT) or via an ioctl call issued
 *	by the superuser.
 */
//添加路由表项 
void ip_rt_add(short flags, unsigned long dst, unsigned long mask,
	unsigned long gw, struct device *dev, unsigned short mtu, unsigned long window, unsigned short irtt)
{
	struct rtable *r, *rt;
	struct rtable **rp;
	unsigned long cpuflags;

	/*
	 *	A host is a unique machine and has no network bits.
	 */
	 
	if (flags & RTF_HOST) //如果是到大主机的表项，则mask设置成255.255.255.255
	{
		mask = 0xffffffff;
	} 
	
	/*
	 *	Calculate the network mask
	 */
	 
	else if (!mask) //如果mask为0，用户层未填写mask或传入的mask不合法
	{
		//判断目的地址是否和本机在同一个网段，如果目的和本机在同一网段，则此表项不能设置RTF_GATEWAY，
		//在同一网段，主机之间可以直接通信，不用先将数据发送给网关，再有网关发送给目的地址，以减轻网关负担
		if (!((dst ^ dev->pa_addr) & dev->pa_mask))
		{
			//在同一网段
			mask = dev->pa_mask;//mask设置为dev->pa_mask
			flags &= ~RTF_GATEWAY; //将RTF_GATEWAY位清除
			if (flags & RTF_DYNAMIC) 
			{
				/*printk("Dynamic route to my own net rejected\n");*/
				return;
			}
		} 
		else
			mask = guess_mask(dst, dev);//如果不在同一网段，通过guess_mask来获取dst的mask
		dst &= mask;//因为走到此处，flags是未设置RTF_HOST标志的，所以，此处意在将主机号去掉
	}
	
	/*
	 *	A gateway must be reachable and not a local address
	 */
	 
	if (gw == dev->pa_addr)//网关ip不能是本机ip，不然发送数据就循环了
		flags &= ~RTF_GATEWAY;
		
	if (flags & RTF_GATEWAY) //如果flags设置RTF_GATEWAY
	{
		/*
		 *	Don't try to add a gateway we can't reach.. 
		 */
		 
		if (dev != get_gw_dev(gw))//判断网关是否可以到达
			return;
			
		flags |= RTF_GATEWAY;//此语句没用
	} 
	else
		gw = 0; //否则gw清0
		
	/*
	 *	Allocate an entry and fill it in.
	 */
	//分配rttable，并根据上面的值对其初始化
	rt = (struct rtable *) kmalloc(sizeof(struct rtable), GFP_ATOMIC);
	if (rt == NULL) 
	{
		return;
	}
	memset(rt, 0, sizeof(struct rtable));
	rt->rt_flags = flags | RTF_UP;
	rt->rt_dst = dst;  //route -nee 看到的destination
	rt->rt_dev = dev;  //Iface
	rt->rt_gateway = gw; //Gateway
	rt->rt_mask = mask;  //Genmask
	rt->rt_mss = dev->mtu - HEADER_SIZE;
	rt->rt_window = 0;	/* Default is no clamping */

	/* Are the MSS/Window valid ? */

	if(rt->rt_flags & RTF_MSS)
		rt->rt_mss = mtu;
		
	if(rt->rt_flags & RTF_WINDOW)
		rt->rt_window = window;
	if(rt->rt_flags & RTF_IRTT)
		rt->rt_irtt = irtt;

	/*
	 *	What we have to do is loop though this until we have
	 *	found the first address which has a higher generality than
	 *	the one in rt.  Then we can put rt in right before it.
	 *	The interrupts must be off for this process.
	 */

	save_flags(cpuflags);
	cli();

	/*
	 *	Remove old route if we are getting a duplicate. 
	 */
	//查找路由表项中是否添加过一样的路由表项，如果添加过，将其删除，对比的条件是dst和mask，两个都相等，则将其删除 
	rp = &rt_base;
	while ((r = *rp) != NULL) 
	{
		if (r->rt_dst != dst || 
		    r->rt_mask != mask) 
		{
			rp = &r->rt_next;
			continue;
		}
		*rp = r->rt_next;
		if (rt_loopback == r)
			rt_loopback = NULL;
		kfree_s(r, sizeof(struct rtable));
	}
	
	/*
	 *	Add the new route 
	 */
	//将新建好的路由表项，添加到链表中。注意：此处不是简单的头插或尾插，而是根据mask的大小来插入的
	//mask越大的越在前面，mask为0的在最后，在查找路由表时，默认路由表项就会最后一个去匹配，这是符合逻辑的
	rp = &rt_base;
	while ((r = *rp) != NULL) {  //循环路由表链表
		if ((r->rt_mask & mask) != mask) //此处判断链表中的mask和新创建的路由表项的mask的大小，例如当r->rt_mask是255.255.0.0，mask是255.255.255.0
			break;                       //时，条件成立跳出循环，当r->rt_mask是255.255.255.0，mask是255.255.0.0时，条件不成立，继续循环，直到找到
		rp = &r->rt_next;                //r->rt_mask小于mask时跳出
	}
	//现在将新建的rt的next成员指向r，
	rt->rt_next = r;
	*rp = rt; //然后*rp指向rt，rt在r的前面。实现了降序排列
	
	/*
	 *	Update the loopback route
	 */
	//如果新创建的路由表项中设备是lo，则将此路由表项同时添加到rt_loopback 
	if ((rt->rt_dev->flags & IFF_LOOPBACK) && !rt_loopback)
		rt_loopback = rt;

	rt_stamp++;		/* New table revision *///路由表更新了，rt_stamp++
		
	/*
	 *	Restore the interrupts and return
	 */
	 
	restore_flags(cpuflags);
	return;
}


/*
 *	Check if a mask is acceptable.
 */
//判断mask是否合法
//必须前面的都是1
static inline int bad_mask(unsigned long mask, unsigned long addr)
{
	if (addr & (mask = ~mask))
		return 1;
	mask = ntohl(mask);
	if (mask & (mask+1))
		return 1;
	return 0;
}

/*
 *	Process a route add request from the user
 */
//用户层调用ioctl系统调用，cmd参数是SIOCADDRT时，调用此函数，进行路由表项的创建
//@rtentry:用户层调用route命令是传入的参数选项放入此结构体中，然后传入内核
static int rt_new(struct rtentry *r)
{
	int err;
	char * devname;
	struct device * dev = NULL;//网卡设备，先初始化为NULL
	unsigned long flags, daddr, mask, gw;

	/*
	 *	If a device is specified find it.
	 */
	//用户层是否指定网卡设备，用户层传入的是字符串，例如：eth0，内核层将其网卡设备找到
	if ((devname = r->rt_dev) != NULL) 
	{
		err = getname(devname, &devname);//devname指向的是用户层地址，此函数将在内核层分配内存，然后将内容复制过来
		if (err)
			return err;
		dev = dev_get(devname);//根据网卡名称获取网卡设备
		putname(devname);
		if (!dev)
			return -EINVAL;
	}
	
	/*
	 *	If the device isn't INET, don't allow it
	 */
	//协议域是否支持，当前版本只支持AF_INET域，即IPV4
	if (r->rt_dst.sa_family != AF_INET)
		return -EAFNOSUPPORT;

	/*
	 *	Make local copies of the important bits
	 */
	 
	flags = r->rt_flags;
	daddr = ((struct sockaddr_in *) &r->rt_dst)->sin_addr.s_addr;
	mask = ((struct sockaddr_in *) &r->rt_genmask)->sin_addr.s_addr;
	gw = ((struct sockaddr_in *) &r->rt_gateway)->sin_addr.s_addr;


	/*
	 *	BSD emulation: Permits route add someroute gw one-of-my-addresses
	 *	to indicate which iface. Not as clean as the nice Linux dev technique
	 *	but people keep using it... 
	 */
	//如果用户层未指定网卡设备，且flags设置了RTF_GATEWAY，则在设置一个间接路由，网关则不能与本机任何一个网卡ip相同，不然发送数据产生循环，自己发送给自己 
	if (!dev && (flags & RTF_GATEWAY)) 
	{
		struct device *dev2;
		for (dev2 = dev_base ; dev2 != NULL ; dev2 = dev2->next) 
		{
			if ((dev2->flags & IFF_UP) && dev2->pa_addr == gw) //如果gw和dev2网卡ip相同
			{
				flags &= ~RTF_GATEWAY;//将RTF_GATEWAY标志清除掉，将其设置为直接路由
				dev = dev2; //dev设在为dev2
				break;
			}
		}
	}

	/*
	 *	Ignore faulty masks
	 */
	//判断用户层传入的mask是否合法，不合法置为0 
	if (bad_mask(mask, daddr))
		mask = 0;

	/*
	 *	Set the mask to nothing for host routes.
	 */
	 
	if (flags & RTF_HOST)
		mask = 0xffffffff;
	else if (mask && r->rt_genmask.sa_family != AF_INET)
		return -EAFNOSUPPORT;

	/*
	 *	You can only gateway IP via IP..
	 */
	//当要增加间接路由时，网卡设备是通过查找路由表来获取的，即get_gw_dev函数来实现
	if (flags & RTF_GATEWAY) 
	{
		if (r->rt_gateway.sa_family != AF_INET)
			return -EAFNOSUPPORT;
		if (!dev)
			dev = get_gw_dev(gw);//获取网卡设备，gw必须是网络可以到达的，即gw和返回的dev的ip地址在同一网段，查找过程详见get_gw_dev函数解释
	} 
	else if (!dev)
		dev = ip_dev_check(daddr);//如果flags未设置RTF_GATEWAY，且dev未指定，则通过ip_dev_check函数来获取，

	/*
	 *	Unknown device.
	 */
	 
	if (dev == NULL) //如果dev未初始化成功，可返回网络不可到达错误
		return -ENETUNREACH;

	/*
	 *	Add the route
	 */
	//添加路由表项
	ip_rt_add(flags, daddr, mask, gw, dev, r->rt_mss, r->rt_window, r->rt_irtt);
	return 0;
}


/*
 *	Remove a route, as requested by the user.
 */
//用户层调用ioctl，cmd参数是SIOCDELRT时，调用此函数，来进行删除一个路由表项
static int rt_kill(struct rtentry *r)
{
	struct sockaddr_in *trg;
	char *devname;
	int err;

	trg = (struct sockaddr_in *) &r->rt_dst;
	if ((devname = r->rt_dev) != NULL) 
	{
		err = getname(devname, &devname);
		if (err)
			return err;
	}
	rt_del(trg->sin_addr.s_addr, devname);
	if ( devname != NULL )
		putname(devname);
	return 0;
}


/* 
 *	Called from the PROCfs module. This outputs /proc/net/route.
 */
//用户层调用route -nee来获取路由表信息时调用此函数 
int rt_get_info(char *buffer, char **start, off_t offset, int length)
{
	struct rtable *r;
	int len=0;
	off_t pos=0;
	off_t begin=0;
	int size;

	len += sprintf(buffer,
		 "Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\n");
	pos=len;
  
	/*
	 *	This isn't quite right -- r->rt_dst is a struct! 
	 */
	 
	for (r = rt_base; r != NULL; r = r->rt_next) 
	{
        	size = sprintf(buffer+len, "%s\t%08lX\t%08lX\t%02X\t%d\t%lu\t%d\t%08lX\t%d\t%lu\t%u\n",
			r->rt_dev->name, r->rt_dst, r->rt_gateway,
			r->rt_flags, r->rt_refcnt, r->rt_use, r->rt_metric,
			r->rt_mask, (int)r->rt_mss, r->rt_window, (int)r->rt_irtt);
		len+=size;
		pos+=size;
		if(pos<offset)
		{
			len=0;
			begin=pos;
		}
		if(pos>offset+length)
			break;
  	}
  	
  	*start=buffer+(offset-begin);
  	len-=(offset-begin);
  	if(len>length)
  		len=length;
  	return len;
}

/*
 *	This is hackish, but results in better code. Use "-S" to see why.
 */
 
#define early_out ({ goto no_route; 1; })

/*
 *	Route a packet. This needs to be fairly quick. Florian & Co. 
 *	suggested a unified ARP and IP routing cache. Done right its
 *	probably a brilliant idea. I'd actually suggest a unified
 *	ARP/IP routing/Socket pointer cache. Volunteers welcome
 */
//全路由匹配
//不仅仅查找路由表，如果daddr是个广播地址，还要匹配哪个路由表项中未设置RTF_GATEWAY标志的dev->pa_brdaddr与其相同，相同，返回此表项
struct rtable * ip_rt_route(unsigned long daddr, struct options *opt, unsigned long *src_addr)
{
	struct rtable *rt;

	for (rt = rt_base; rt != NULL || early_out ; rt = rt->rt_next) 
	{
		if (!((rt->rt_dst ^ daddr) & rt->rt_mask)) //1、当在同一网段时，找到路由表项，跳出，直接发送给目标机器。2、不在同一网段，rt->rt_mask == 0,找到路由表项，跳出，走网关。等效 !((rt->rt_dst&rt->mask)^(daddr&rt->rt_mask)) 
			break;
		/*
		 *	broadcast addresses can be special cases.. 
		 */
		if (rt->rt_flags & RTF_GATEWAY)
			continue;		 
		if ((rt->rt_dev->flags & IFF_BROADCAST) &&  //与广播地址匹配
		    (rt->rt_dev->pa_brdaddr == daddr))
			break;
	}
	//路由表项是否设置拒绝路由标志，
	if(rt->rt_flags&RTF_REJECT)
		return NULL;
	
	if(src_addr!=NULL) //是否需要返回源ip地址
		*src_addr= rt->rt_dev->pa_addr;
		
	if (daddr == rt->rt_dev->pa_addr) {
		if ((rt = rt_loopback) == NULL)
			goto no_route;
	}
	rt->rt_use++;
	return rt;
no_route:
	return NULL;
}
//直接路由匹配
//此匹配函数不会去匹配带有RTF_GATEWAY标志的路由表项
struct rtable * ip_rt_local(unsigned long daddr, struct options *opt, unsigned long *src_addr)
{
	struct rtable *rt;

	for (rt = rt_base; rt != NULL || early_out ; rt = rt->rt_next) 
	{
		/*
		 *	No routed addressing.
		 */
		if (rt->rt_flags&RTF_GATEWAY) //如果路由表项设置了RTF_GATEWAY标志，继续循环，不做匹配
			continue;
			
		if (!((rt->rt_dst ^ daddr) & rt->rt_mask)) //当在同一网段时，找到路由表项，跳出，直接发送给目标机器。
			break;
		/*
		 *	broadcast addresses can be special cases.. 
		 */
		 
		if ((rt->rt_dev->flags & IFF_BROADCAST) &&
		     rt->rt_dev->pa_brdaddr == daddr)
			break;
	}
	
	if(src_addr!=NULL)
		*src_addr= rt->rt_dev->pa_addr;
		
	if (daddr == rt->rt_dev->pa_addr) {
		if ((rt = rt_loopback) == NULL)
			goto no_route;
	}
	rt->rt_use++;
	return rt;
no_route:
	return NULL;
}

/*
 *	Handle IP routing ioctl calls. These are used to manipulate the routing tables
 */
//关于路由操作的ioctl函数，添加和删除路由表项
/* 用户层传入内核的路由参数
struct rtentry {
	unsigned long	rt_hash;	// hash key for lookups
	struct sockaddr	rt_dst;		// target address 目的地址
	struct sockaddr	rt_gateway;	// gateway addr (RTF_GATEWAY)  网关地址
	struct sockaddr	rt_genmask;	// target network mask (IP) 掩码
	short		rt_flags;       // 标志
	short		rt_refcnt;
	unsigned long	rt_use;
	struct ifnet	*rt_ifp;
	short		rt_metric;	    // +1 for binary compatibility!
	char		*rt_dev;	    // forcing the device at add
	unsigned long	rt_mss;		// per route MTU/Window
	unsigned long	rt_window;	// Window clamping
	unsigned short	rt_irtt;	// Initial RTT
};
*/
int ip_rt_ioctl(unsigned int cmd, void *arg)
{
	int err;
	struct rtentry rt;

	switch(cmd) 
	{
		case SIOCADDRT:		/* Add a route */
		case SIOCDELRT:		/* Delete a route */
			if (!suser())
				return -EPERM;
			err=verify_area(VERIFY_READ, arg, sizeof(struct rtentry));
			if (err)
				return err;
			memcpy_fromfs(&rt, arg, sizeof(struct rtentry));
			return (cmd == SIOCDELRT) ? rt_kill(&rt) : rt_new(&rt);
	}

	return -EINVAL;
}
